package org.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author huangshen
 * @version 0.0.3
 * @description ZipDirectorUtil
 * @since 2022/1/6 20:56
 */
public class ZipDirectorUtil {

    private static final int BUFFER_SIZE = 2 * 1024;

    private ZipDirectorUtil() {
    }

    /**
     * @param srcDir           压缩文件夹路径集合
     * @param outDir           压缩文件输出文件名 zip
     * @param keepDirStructure 是否保留原来的目录结构,
     *                         true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toZip(List<String> srcDir, String outDir,
                             boolean keepDirStructure) throws Exception {
        OutputStream out = new FileOutputStream(outDir);
        try (ZipOutputStream zos = new ZipOutputStream(out)) {
            List<File> sourceFileList = new ArrayList<>();
            for (String dir : srcDir) {
                File sourceFile = new File(dir);
                sourceFileList.add(sourceFile);
            }
            compress(sourceFileList, zos, keepDirStructure);
        } catch (Exception e) {
            throw new RuntimeException("zip error from ZipUtils", e);
        }
    }

    /**
     * 压缩方法
     *
     * @param sourceFileList   目标文件集合
     * @param zos              zip输出流
     * @param keepDirStructure 是否保留原来的目录结构
     *                         true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws Exception
     */
    private static void compress(List<File> sourceFileList,
                                 ZipOutputStream zos, boolean keepDirStructure) throws Exception {
        byte[] buf = new byte[BUFFER_SIZE];
        for (File sourceFile : sourceFileList) {
            String name = sourceFile.getName();
            transferFiles(zos, keepDirStructure, buf, sourceFile, name);
        }
    }

    /**
     * 递归压缩方法
     *
     * @param sourceFile       源文件
     * @param zos              zip输出流
     * @param name             压缩文件名
     * @param keepDirStructure 是否保留原来的目录结构,
     *                         true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     */
    private static void compress(File sourceFile, ZipOutputStream zos,
                                 String name, boolean keepDirStructure) throws Exception {
        byte[] buf = new byte[BUFFER_SIZE];
        transferFiles(zos, keepDirStructure, buf, sourceFile, name);
    }

    /**
     * 文件传输
     *
     * @param zos              zip输出流
     * @param keepDirStructure 是否保留原来的目录结构
     * @param buf              数据传输buffer数组
     * @param sourceFile       目标文件
     * @param name             压缩文件名
     * @throws Exception
     */
    private static void transferFiles(ZipOutputStream zos, boolean keepDirStructure, byte[] buf, File sourceFile, String name) throws Exception {
        if (sourceFile.isFile()) {
            zos.putNextEntry(new ZipEntry(name));
            int len;
            try (
                    FileInputStream in = new FileInputStream(sourceFile)
            ) {
                while ((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }
                zos.closeEntry();
            } catch (Exception e) {
                throw new RuntimeException("zip error from ZipUtils", e);
            }
            return;
        }
        File[] listFiles = sourceFile.listFiles();
        if (listFiles != null && listFiles.length != 0) {
            for (File file : listFiles) {
                if (keepDirStructure) {
                    compress(file, zos, name + "/" + file.getName(),
                            true);
                } else {
                    compress(file, zos, file.getName(),
                            false);
                }
            }
            return;
        }
        if (keepDirStructure) {
            zos.putNextEntry(new ZipEntry(name + "/"));
            zos.closeEntry();
        }
    }
}

